Malware Scanner Dashboard Integration Plan
Project: Integrate Alfred malware scanning reports into Security Dashboard Target Dashboard: https://8qdj5it341kfv92u.brandonquig.com/security-dashboard/ Status: Planning Phase Created: 2026-03-06
Executive Summary
Integrate malware scanning system (ClamAV, maldet, rkhunter, chkrootkit, Lynis) into the existing Security Dashboard to provide real-time visibility of system security posture, automated scan results, and threat detection.
Goals: 1. Display malware scan results in Security Dashboard 2. Incorporate scan metrics into overall security posture score 3. Provide historical trending of scan results 4. Alert on detected threats 5. Enable drill-down into scan logs
Current State Analysis
Existing Security Dashboard Architecture
Location: /var/www/html/alfred/dashboard/security-dashboard/
Structure:
security-dashboard/
βββ index.php # Main dashboard UI
βββ api/
β βββ posture.php # Overall security posture score
β βββ compliance.php # Compliance controls
β βββ incidents.php # Security incidents
β βββ redteam.php # Red team attack results
β βββ alerts.php # Security alerts
β βββ lib/
β βββ db.php # Database connection
βββ css/
β βββ security.css # Dashboard styling
βββ js/
βββ (JavaScript modules)
Database: PostgreSQL blueteam schema
- posture_scores - Historical security scores
- compliance_controls - Compliance tracking
- security_incidents - Incident management
- (Red team reports stored as JSON files)
Posture Score Calculation:
Overall Score =
Compliance (35%) +
Red Team (30%) +
Incident (20%) +
Monitoring (15%)
Malware Scanning System
Location: /opt/claude-workspace/shared-resources/scripts/
Tools Installed: - ClamAV - Daily antivirus scans (2 AM) - maldet - Daily web malware scans (3 AM) - rkhunter - Weekly rootkit scans (Sunday 4 AM) - chkrootkit - Weekly rootkit verification (Sunday 4:30 AM) - Lynis - On-demand security audits
Log Files: /var/log/malware-scans/
- clamav-YYYYMMDD.log - Daily
- maldet-YYYYMMDD.log - Daily
- rkhunter-YYYYMMDD.log - Weekly
- chkrootkit-YYYYMMDD.log - Weekly
Current Gap: Scan results not visible in dashboard, no database integration
Proposed Architecture
1. Database Schema Extension
New Table: blueteam.malware_scans
CREATE TABLE blueteam.malware_scans (
scan_id SERIAL PRIMARY KEY,
scan_type VARCHAR(20) NOT NULL, -- 'clamav', 'maldet', 'rkhunter', 'chkrootkit', 'lynis'
scan_date TIMESTAMP NOT NULL DEFAULT NOW(),
status VARCHAR(20) NOT NULL, -- 'clean', 'infected', 'warning', 'error'
files_scanned INTEGER,
infections_found INTEGER DEFAULT 0,
scan_duration_seconds INTEGER,
log_file_path TEXT,
summary JSONB, -- Detailed scan metrics
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_malware_scans_date ON blueteam.malware_scans(scan_date DESC);
CREATE INDEX idx_malware_scans_type ON blueteam.malware_scans(scan_type);
CREATE INDEX idx_malware_scans_status ON blueteam.malware_scans(status);
New Table: blueteam.malware_detections
CREATE TABLE blueteam.malware_detections (
detection_id SERIAL PRIMARY KEY,
scan_id INTEGER REFERENCES blueteam.malware_scans(scan_id),
file_path TEXT NOT NULL,
malware_signature VARCHAR(255),
severity VARCHAR(20), -- 'critical', 'high', 'medium', 'low'
action_taken VARCHAR(50), -- 'quarantined', 'deleted', 'reported', 'none'
detected_at TIMESTAMP NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMP,
resolution_notes TEXT
);
CREATE INDEX idx_malware_detections_scan ON blueteam.malware_detections(scan_id);
CREATE INDEX idx_malware_detections_unresolved ON blueteam.malware_detections(resolved_at) WHERE resolved_at IS NULL;
Schema Updates: blueteam.posture_scores
-- Add malware_score column
ALTER TABLE blueteam.posture_scores
ADD COLUMN malware_score NUMERIC(5,2);
-- Update posture calculation to include malware
-- New formula:
-- Overall = Compliance(30%) + RedTeam(25%) + Incident(20%) + Monitoring(15%) + Malware(10%)
2. Log Parser Service
Purpose: Parse malware scan logs and populate database
Location: /opt/claude-workspace/shared-resources/scripts/parse-malware-logs.py
Functionality:
#!/usr/bin/env python3
"""
Parse malware scan logs and insert into PostgreSQL database.
Run after each automated scan completes.
"""
import re
import json
from datetime import datetime
from pathlib import Path
import psycopg2
LOG_DIR = Path("/var/log/malware-scans")
DB_CONFIG = {
"host": "localhost",
"database": "blueteam",
"user": "blueteam_app",
"password": "from_config_or_env"
}
class ClamAVParser:
"""Parse ClamAV scan logs."""
def parse(self, log_file: Path) -> dict:
"""Extract scan metrics from ClamAV log."""
with open(log_file) as f:
content = f.read()
# Extract summary section
summary = {}
if match := re.search(r"Known viruses: (\d+)", content):
summary['known_viruses'] = int(match.group(1))
if match := re.search(r"Scanned files: (\d+)", content):
summary['files_scanned'] = int(match.group(1))
if match := re.search(r"Infected files: (\d+)", content):
summary['infected_files'] = int(match.group(1))
if match := re.search(r"Time: ([\d.]+) sec", content):
summary['duration_seconds'] = int(float(match.group(1)))
# Extract infected files
infections = []
for match in re.finditer(r"^(.+): (.+) FOUND$", content, re.MULTILINE):
infections.append({
'file_path': match.group(1).strip(),
'signature': match.group(2).strip(),
'severity': self._assess_severity(match.group(2))
})
status = 'infected' if infections else 'clean'
return {
'scan_type': 'clamav',
'status': status,
'files_scanned': summary.get('files_scanned', 0),
'infections_found': len(infections),
'scan_duration_seconds': summary.get('duration_seconds', 0),
'log_file_path': str(log_file),
'summary': summary,
'infections': infections
}
def _assess_severity(self, signature: str) -> str:
"""Determine severity based on malware signature."""
sig_lower = signature.lower()
if any(x in sig_lower for x in ['backdoor', 'trojan', 'ransomware', 'rootkit']):
return 'critical'
elif any(x in sig_lower for x in ['webshell', 'exploit', 'malware']):
return 'high'
elif 'suspicious' in sig_lower:
return 'medium'
else:
return 'low'
class MaldetParser:
"""Parse maldet (Linux Malware Detect) logs."""
# Similar structure to ClamAVParser
class RkhunterParser:
"""Parse rkhunter logs."""
# Parse warning counts, suspect files
class ChkrootkitParser:
"""Parse chkrootkit logs."""
# Parse INFECTED markers
def insert_scan_results(db_conn, scan_data: dict):
"""Insert scan results into database."""
with db_conn.cursor() as cur:
# Insert main scan record
cur.execute("""
INSERT INTO blueteam.malware_scans
(scan_type, scan_date, status, files_scanned, infections_found,
scan_duration_seconds, log_file_path, summary)
VALUES (%(scan_type)s, NOW(), %(status)s, %(files_scanned)s,
%(infections_found)s, %(scan_duration_seconds)s,
%(log_file_path)s, %(summary)s::jsonb)
RETURNING scan_id
""", scan_data)
scan_id = cur.fetchone()[0]
# Insert individual detections
if scan_data.get('infections'):
for infection in scan_data['infections']:
cur.execute("""
INSERT INTO blueteam.malware_detections
(scan_id, file_path, malware_signature, severity, action_taken)
VALUES (%s, %s, %s, %s, %s)
""", (scan_id, infection['file_path'], infection['signature'],
infection['severity'], 'reported'))
db_conn.commit()
return scan_id
def main():
"""Parse recent logs and update database."""
conn = psycopg2.connect(**DB_CONFIG)
# Parse today's ClamAV log
today = datetime.now().strftime("%Y%m%d")
clamav_log = LOG_DIR / f"clamav-{today}.log"
if clamav_log.exists():
parser = ClamAVParser()
scan_data = parser.parse(clamav_log)
insert_scan_results(conn, scan_data)
print(f"Parsed ClamAV: {scan_data['status']}, "
f"{scan_data['files_scanned']} files, "
f"{scan_data['infections_found']} infections")
# Parse maldet, rkhunter, chkrootkit similarly
conn.close()
if __name__ == "__main__":
main()
Integration: Add to automated scan scripts
# In /usr/local/bin/clamav-daily-scan.sh, after scan completes:
/usr/bin/python3 /opt/claude-workspace/shared-resources/scripts/parse-malware-logs.py
3. Dashboard API Endpoint
New File: /var/www/html/alfred/dashboard/security-dashboard/api/malware.php
<?php
header('Content-Type: application/json');
$userId = $_SERVER['HTTP_X_AUTH_USER_ID'] ?? null;
if (!$userId) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
require_once __DIR__ . '/lib/db.php';
try {
$pdo = getSecurityDb();
// Get latest scan results by type
$stmt = $pdo->query("
SELECT
scan_type,
scan_date,
status,
files_scanned,
infections_found,
scan_duration_seconds,
summary
FROM blueteam.malware_scans
WHERE scan_id IN (
SELECT MAX(scan_id)
FROM blueteam.malware_scans
GROUP BY scan_type
)
ORDER BY scan_type
");
$latest_scans = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get recent detections (last 30 days, unresolved)
$stmt = $pdo->query("
SELECT
d.detection_id,
d.file_path,
d.malware_signature,
d.severity,
d.action_taken,
d.detected_at,
s.scan_type
FROM blueteam.malware_detections d
JOIN blueteam.malware_scans s ON d.scan_id = s.scan_id
WHERE d.resolved_at IS NULL
AND d.detected_at > NOW() - INTERVAL '30 days'
ORDER BY d.detected_at DESC
LIMIT 50
");
$active_detections = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get scan history (last 30 days)
$stmt = $pdo->query("
SELECT
scan_type,
DATE(scan_date) as scan_day,
COUNT(*) as scan_count,
SUM(infections_found) as total_infections,
SUM(files_scanned) as total_files
FROM blueteam.malware_scans
WHERE scan_date > NOW() - INTERVAL '30 days'
GROUP BY scan_type, DATE(scan_date)
ORDER BY scan_day DESC, scan_type
");
$scan_history = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Calculate malware score
// Score = 100 - (critical_infections * 30 + high * 20 + medium * 10 + low * 5)
$stmt = $pdo->query("
SELECT
severity,
COUNT(*) as count
FROM blueteam.malware_detections
WHERE resolved_at IS NULL
GROUP BY severity
");
$severity_counts = ['critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$severity_counts[$row['severity']] = (int)$row['count'];
}
$malware_score = max(0, 100 - (
$severity_counts['critical'] * 30 +
$severity_counts['high'] * 20 +
$severity_counts['medium'] * 10 +
$severity_counts['low'] * 5
));
// Days since last scan by type
$stmt = $pdo->query("
SELECT
scan_type,
EXTRACT(EPOCH FROM (NOW() - MAX(scan_date)))/86400 as days_since
FROM blueteam.malware_scans
GROUP BY scan_type
");
$last_scan_days = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$last_scan_days[$row['scan_type']] = round((float)$row['days_since'], 1);
}
echo json_encode([
'malware_score' => $malware_score,
'latest_scans' => $latest_scans,
'active_detections' => $active_detections,
'scan_history' => $scan_history,
'severity_counts' => $severity_counts,
'last_scan_days' => $last_scan_days
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database query failed']);
}
4. Dashboard UI Components
4.1 Update Posture Tab
Add malware score card to /var/www/html/alfred/dashboard/security-dashboard/index.php:
<!-- In posture tab, add 5th score card -->
<div class="score-card score-card-malware">
<div class="score-card-value" id="score-malware">--</div>
<div class="score-card-label">Malware</div>
<div class="score-card-weight">10%</div>
</div>
4.2 Add Malware Tab
New tab in navigation:
<nav class="tab-nav">
<a href="#posture" class="active">Posture</a>
<a href="#compliance">Compliance</a>
<a href="#incidents">Incidents</a>
<a href="#redteam">Red Team</a>
<a href="#malware">Malware</a> <!-- NEW -->
</nav>
4.3 Malware Tab Content
<!-- Tab 5: Malware -->
<div id="tab-malware" class="tab-content">
<!-- Summary Cards -->
<div class="malware-summary">
<div class="summary-card">
<div class="summary-icon">π‘οΈ</div>
<div class="summary-value" id="malware-score-display">--</div>
<div class="summary-label">Malware Defense Score</div>
</div>
<div class="summary-card">
<div class="summary-icon">π</div>
<div class="summary-value" id="total-scans">--</div>
<div class="summary-label">Scans Today</div>
</div>
<div class="summary-card summary-card-danger" id="active-threats-card">
<div class="summary-icon">β οΈ</div>
<div class="summary-value" id="active-threats">--</div>
<div class="summary-label">Active Threats</div>
</div>
<div class="summary-card">
<div class="summary-icon">π</div>
<div class="summary-value" id="files-scanned">--</div>
<div class="summary-label">Files Scanned (24h)</div>
</div>
</div>
<!-- Latest Scan Results -->
<div class="section-title">Latest Scan Results</div>
<div class="scan-results-grid" id="scan-results-grid">
<!-- Populated by JavaScript -->
</div>
<!-- Active Detections -->
<div class="section-title">
Active Detections
<span class="count-badge" id="detections-count">0</span>
</div>
<div class="detections-table-container">
<table class="detections-table" id="detections-table">
<thead>
<tr>
<th>Severity</th>
<th>File Path</th>
<th>Malware Signature</th>
<th>Detected</th>
<th>Scanner</th>
<th>Action</th>
<th>Options</th>
</tr>
</thead>
<tbody id="detections-tbody">
<!-- Populated by JavaScript -->
</tbody>
</table>
</div>
<!-- Scan History Chart -->
<div class="section-title">30-Day Scan History</div>
<div class="chart-container">
<canvas id="malware-history-chart"></canvas>
</div>
</div>
4.4 JavaScript Integration
// In security dashboard JS
async function loadMalwareData() {
try {
const response = await fetch('/security-dashboard/api/malware.php', {
headers: { 'X-Auth-User-Id': userId }
});
const data = await response.json();
// Update score
document.getElementById('score-malware').textContent = data.malware_score;
document.getElementById('malware-score-display').textContent = data.malware_score;
// Update summary cards
document.getElementById('active-threats').textContent =
Object.values(data.severity_counts).reduce((a, b) => a + b, 0);
// Populate scan results grid
renderScanResults(data.latest_scans);
// Populate detections table
renderDetections(data.active_detections);
// Render history chart
renderMalwareHistoryChart(data.scan_history);
} catch (error) {
console.error('Failed to load malware data:', error);
}
}
function renderScanResults(scans) {
const grid = document.getElementById('scan-results-grid');
grid.innerHTML = scans.map(scan => `
<div class="scan-result-card scan-${scan.status}">
<div class="scan-header">
<span class="scan-type">${scan.scan_type.toUpperCase()}</span>
<span class="scan-status status-${scan.status}">${scan.status}</span>
</div>
<div class="scan-metrics">
<div class="metric">
<span class="metric-value">${scan.files_scanned.toLocaleString()}</span>
<span class="metric-label">Files Scanned</span>
</div>
<div class="metric">
<span class="metric-value ${scan.infections_found > 0 ? 'danger' : ''}">${scan.infections_found}</span>
<span class="metric-label">Infections</span>
</div>
</div>
<div class="scan-footer">
<span class="scan-time">${formatTimestamp(scan.scan_date)}</span>
<a href="#" onclick="viewScanLog('${scan.scan_type}')" class="scan-log-link">View Log</a>
</div>
</div>
`).join('');
}
function renderDetections(detections) {
const tbody = document.getElementById('detections-tbody');
document.getElementById('detections-count').textContent = detections.length;
if (detections.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="no-data">No active detections</td></tr>';
return;
}
tbody.innerHTML = detections.map(d => `
<tr class="detection-row severity-${d.severity}">
<td><span class="severity-badge severity-${d.severity}">${d.severity}</span></td>
<td class="file-path"><code>${d.file_path}</code></td>
<td class="signature">${d.malware_signature}</td>
<td>${formatTimestamp(d.detected_at)}</td>
<td>${d.scan_type}</td>
<td>${d.action_taken}</td>
<td>
<button onclick="resolveDetection(${d.detection_id})" class="btn-resolve">Resolve</button>
<button onclick="viewDetails(${d.detection_id})" class="btn-details">Details</button>
</td>
</tr>
`).join('');
}
5. Alert Integration
Update: /var/www/html/alfred/dashboard/security-dashboard/api/alerts.php
Add malware detections to alerts:
// Get recent malware detections
$stmt = $pdo->query("
SELECT
'malware' as type,
'critical' as severity,
d.detected_at as timestamp,
CONCAT('Malware detected: ', d.malware_signature, ' in ',
SUBSTRING(d.file_path FROM '[^/]+$')) as message,
JSON_BUILD_OBJECT(
'detection_id', d.detection_id,
'file_path', d.file_path,
'scanner', s.scan_type
) as metadata
FROM blueteam.malware_detections d
JOIN blueteam.malware_scans s ON d.scan_id = s.scan_id
WHERE d.resolved_at IS NULL
AND d.severity IN ('critical', 'high')
AND d.detected_at > NOW() - INTERVAL '7 days'
ORDER BY d.detected_at DESC
LIMIT 10
");
$malware_alerts = $stmt->fetchAll(PDO::FETCH_ASSOC);
6. Updated Posture Score Calculation
Update: /var/www/html/alfred/dashboard/security-dashboard/api/posture.php
// Add malware score calculation
$stmt = $pdo->query("
SELECT
severity,
COUNT(*) as count
FROM blueteam.malware_detections
WHERE resolved_at IS NULL
GROUP BY severity
");
$severity_counts = ['critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0];
while ($row = $stmt->fetch()) {
$severity_counts[$row['severity']] = (int)$row['count'];
}
$malwareScore = max(0, 100 - (
$severity_counts['critical'] * 30 +
$severity_counts['high'] * 20 +
$severity_counts['medium'] * 10 +
$severity_counts['low'] * 5
));
// Update overall score calculation
$overallScore = round(
$complianceScore * 0.30 + // Reduced from 35%
$redteamScore * 0.25 + // Reduced from 30%
$incidentScore * 0.20 +
$monitoringScore * 0.15 +
$malwareScore * 0.10, // NEW
2
);
$current['malware'] = (float) $malwareScore;
Implementation Phases
Phase 1: Database Setup (1-2 hours)
Tasks: 1. Create database schema (tables, indexes) 2. Add sample data for testing 3. Verify database permissions
Deliverables: - SQL migration script - Database schema documentation - Test data fixtures
Phase 2: Log Parser Development (3-4 hours)
Tasks: 1. Write Python log parser for each scanner type 2. Add database insertion logic 3. Create unit tests 4. Integrate with existing scan scripts
Deliverables:
- parse-malware-logs.py script
- Parser unit tests
- Updated scan scripts (clamav-daily-scan.sh, etc.)
Phase 3: API Endpoint (2-3 hours)
Tasks:
1. Create /api/malware.php endpoint
2. Implement query logic for latest scans, detections, history
3. Add malware score calculation
4. Test API responses
Deliverables:
- api/malware.php
- API documentation
- Test cases
Phase 4: UI Components (4-5 hours)
Tasks: 1. Add malware score card to posture tab 2. Create new "Malware" tab 3. Build scan results grid 4. Build detections table 5. Add history chart 6. Style all components
Deliverables:
- Updated index.php
- CSS updates for malware components
- JavaScript modules for data loading/rendering
Phase 5: Integration & Testing (2-3 hours)
Tasks: 1. Update posture score calculation 2. Add malware alerts to alerts system 3. End-to-end testing 4. Fix bugs and polish UI
Deliverables:
- Updated posture.php
- Updated alerts.php
- Test results documentation
Phase 6: Deployment (1 hour)
Tasks: 1. Deploy database schema to production 2. Deploy code to Alfred dashboard 3. Run initial log parsing 4. Verify dashboard displays correctly 5. Monitor for issues
Deliverables: - Deployment checklist - Production verification - Monitoring alerts
Total Effort Estimate
Development: 12-17 hours Testing: 2-3 hours Deployment: 1 hour Total: 15-21 hours (2-3 days)
Technical Requirements
Server Requirements
- Python 3.8+ (already installed)
- PostgreSQL with blueteam database access
- Read access to
/var/log/malware-scans/ - Web server (Apache/Nginx) for PHP
Dependencies
- Python:
psycopg2,python-dateutil - PHP: PDO PostgreSQL extension
- JavaScript: Chart.js (for history chart)
Risk Assessment
High Risk
- Log parsing failures - Scan log formats may vary
-
Mitigation: Robust regex patterns, error handling
-
Database performance - Large scan history could slow queries
- Mitigation: Proper indexing, data retention policies
Medium Risk
- UI complexity - Many data points to display
-
Mitigation: Progressive enhancement, lazy loading
-
Alert fatigue - Too many malware alerts
- Mitigation: Severity filtering, deduplication
Low Risk
- Browser compatibility - Modern JS/CSS features
- Mitigation: Use stable, well-supported features
Success Metrics
- Visibility: All scan results visible in dashboard within 5 minutes of completion
- Accuracy: Malware score correctly reflects active threats
- Performance: Dashboard loads in <2 seconds
- Usability: Users can drill down to individual detections in <3 clicks
- Reliability: Log parsing succeeds >99% of the time
Future Enhancements
Phase 7 (Future)
- Real-time scan progress monitoring
- Manual scan triggers from dashboard
- Automated remediation workflows
- Integration with incident response system
- Compliance mapping (map scans to controls)
- Advanced analytics (trend analysis, ML anomaly detection)
- Mobile notifications for critical detections
- Integration with SIEM systems
Appendices
A. Database ERD
malware_scans
βββ scan_id (PK)
βββ scan_type
βββ scan_date
βββ status
βββ files_scanned
βββ infections_found
βββ scan_duration_seconds
βββ log_file_path
βββ summary (JSONB)
βββ created_at
malware_detections
βββ detection_id (PK)
βββ scan_id (FK -> malware_scans)
βββ file_path
βββ malware_signature
βββ severity
βββ action_taken
βββ detected_at
βββ resolved_at
βββ resolution_notes
posture_scores
βββ ... (existing columns)
βββ malware_score (NEW)
B. API Response Examples
GET /api/malware.php
{
"malware_score": 95.0,
"latest_scans": [
{
"scan_type": "clamav",
"scan_date": "2026-03-06 02:00:00",
"status": "clean",
"files_scanned": 156789,
"infections_found": 0,
"scan_duration_seconds": 1245
}
],
"active_detections": [
{
"detection_id": 42,
"file_path": "/var/www/html/example.com/shell.php",
"malware_signature": "Php.Webshell.Generic",
"severity": "critical",
"action_taken": "quarantined",
"detected_at": "2026-03-05 15:30:00",
"scan_type": "clamav"
}
],
"severity_counts": {
"critical": 1,
"high": 0,
"medium": 0,
"low": 0
},
"last_scan_days": {
"clamav": 0.1,
"maldet": 0.2,
"rkhunter": 2.5
}
}
C. UI Mockups
See attached wireframes for: - Malware tab layout - Scan results cards - Detections table - History chart
Plan Status: READY FOR REVIEW Next Steps: Review plan, obtain approval, begin Phase 1